package top.lingkang.securityoauth2.client.annotation.impl;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import top.lingkang.securityoauth2.client.annotation.FinalCheck;
import top.lingkang.securityoauth2.client.base.FinalAuth;
import top.lingkang.securityoauth2.client.config.Oauth2ClientProperties;
import top.lingkang.securityoauth2.client.constants.FinalConstants;
import top.lingkang.securityoauth2.client.error.FinalOauth2Exception;
import top.lingkang.securityoauth2.client.error.FinalNotLoginException;
import top.lingkang.securityoauth2.client.http.FinalSecurityContext;
import top.lingkang.securityoauth2.client.http.SecurityOauth2Holder;
import top.lingkang.securityoauth2.client.utils.AuthUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author lingkang
 * Created by 2022/1/11
 */
@Aspect
public class FinalCheckAnnotation {
    private final Logger logger = LoggerFactory.getLogger(FinalCheckAnnotation.class);
    @Autowired(required = false)
    private SecurityOauth2Holder securityHolder;
    @Autowired(required = false)
    private Oauth2ClientProperties securityProperties;

    @Around("@within(top.lingkang.securityoauth2.client.annotation.FinalCheck) || @annotation(top.lingkang.securityoauth2.client.annotation.FinalCheck)")
    public Object method(ProceedingJoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = FinalSecurityContext.getRequest();
        if (securityProperties.getCheckPathCache().getExcludePath()
                .contains(request.getServletPath())) {
            return joinPoint.proceed();
        }

        if (!securityHolder.isLogin()) {
            throw new FinalNotLoginException(FinalConstants.NOT_LOGIN_MSG);
        }

        List<FinalAuth> finalAuthList = new ArrayList<>();

        FinalCheck clazz = joinPoint.getTarget().getClass().getAnnotation(FinalCheck.class);
        if (clazz != null) {
            // 添加缓存
            FinalAuth auth = new FinalAuth();
            boolean has = false;
            String[] any = clazz.anyRole();
            if (any.length != 0) {
                has = true;
                auth.setRole(any);
            }

            String[] and = clazz.andRole();
            if (and.length != 0) {
                has = true;
                auth.setAndRole(and);
            }
            if (has)
                finalAuthList.add(auth);
        }

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        FinalCheck method = signature.getMethod().getAnnotation(FinalCheck.class);
        if (method != null) {
            // 添加缓存
            FinalAuth auth = new FinalAuth();
            boolean has = false;
            String[] any = method.anyRole();
            if (any.length != 0) {
                has = true;
                auth.setRole(any);
            }
            String[] and = method.andRole();
            if (and.length != 0) {
                has = true;
                auth.setAndRole(and);
            }
            if (has)
                finalAuthList.add(auth);
        }

        // 对此进行缓存
        if (finalAuthList.isEmpty()) {
            logger.error("未对鉴权注解 @FinalCheck 添加角色！例如正确使用： @FinalCheck(anyRole = \"user\") url=" + request.getServletPath());
            // 如果未配置 角色鉴权 应该抛出异常
            throw new FinalOauth2Exception("Authentication configuration error! 鉴权配置错误！");
        } else {
            // 应该保持原有的鉴权
            FinalAuth[] auths = securityProperties.getCheckPathCache().getAuths().get(request.getServletPath());
            if (auths != null && auths.length != 0) {
                finalAuthList.addAll(Arrays.asList(auths));
            }
            securityProperties.getCheckPathCache().getAuths().put(request.getServletPath(), finalAuthList.toArray(new FinalAuth[0]));
        }

        if (clazz != null)
            check(clazz);
        if (method != null)
            check(method);

        return joinPoint.proceed();
    }

    private void check(FinalCheck check) {
        if (check.anyRole().length != 0)
            AuthUtils.checkRole(check.anyRole(), securityHolder.getRole());
        if (check.andRole().length != 0)
            AuthUtils.checkAndRole(check.andRole(), securityHolder.getRole());
    }
}
